#lang scheme

(define // quotient)
(define % remainder)


(define (is_prime? num)     ; проверка числа на простоту, сложность ~O(sqrt(n)/2)
  (define root (sqrt num))  ; корень числа
  (define (iter k)          
    (if (<= k root)         ; база рекурсии
        (if (= (% num k) 0) ; num mod k == 0?
            #f              ; не простое
            (iter (+ k 2))) ; добавляем 2, т.к. есть смысл проверять только нечетные делители
     #t                     ; делителей в 2,3,5,7,9..sqrt(n) не нашлось - простое
  ))
  (cond
    [ (< num 2)       #f]   ; нет простых чисел меньше 2
    [ (< 1 num 4)     #t]   ; числа 2 и 3 - простые
    [ (= (% num 2) 0) #f]   ; четные числа больше 2 не простые
    [ else (iter 3)])       ; проверяем на простоту числа больше 4
)

(define (z1 number)
  (define (iter num div c)
; num - число (со временем уменьшается), div - делитель (увеличивается), с - счётчик (сколько раз число делилось на div)
    (if (= num 1)                      ; база рекурсии
        (if (> c 0)                    ; если счетчик больше 0 (число делилось на div)
            (cons (cons div c) null)   ; да.  возвращаем '( (div . c) )
            null                       ; нет. '()
        )
        (if (is_prime? num)                                        ; если число простое
            (if (= c 0)                                            ; если число не делилось на div
                (cons (cons num 1) null)                           ; возвращаем '( (num . 1) )
                (if (= num div)                                    ; если число равно делителю
                    (cons (cons div (+ c 1)) null)                 ; да.  возвращаем '( (num . (+ c 1) ), т.к. num // div == 1
                    (cons (cons div c) (cons (cons num 1) null)))) ; нет. возвращаем '( (div . c) (num . 1) )
                 
            (if (= (% num div) 0)                                  ; div делит num
                (iter (// num div) div (+ c 1))                    ; уменьшаем num, сохраняем div, увеличиваем счётчик
                (if (= c 0)                                        ; до этого не было div, который делил бы num
                    (iter num (+ div 1) 0)                         ; сохраняем num, увеличиваем div, обнуляем счётчик
                    (cons (cons div c) (iter num (+ div 1) 0))     ; добавляем (div . c) к результату iter от num и делителей больших текущего div
  )))))
  (iter number 2 0))                                               ; вызываем iter от числа, нач. делитель = 2, счётчик = 0

;--------------------------------------------------------------

(define (find-middle lst num c) ; находим среднее значение списка sum(arr) / len(arr)
  (if (empty? lst)              ; база рекурсии
      (/ num c)                 ; среднее арифметическое
      (find-middle (cdr lst) (+ num (car lst)) (+ c 1)))) ; находим сумму и кол-во элементов, пока список lst не пуст

; middle - среднее арифметическое списка lst
; num    - наименьшее ближайшее число к middle
; lst    - список, по которому пробегаемся
(define (find-nearest-num middle num lst)
  (if (empty? lst)                                                ; база рекурсии
      num                                                         ; наименьшее ближайшее число к middle
      (if (> (abs (- num middle)) (abs (- (car lst) middle)))     ; если первое число из списка ближе к middle
          (find-nearest-num middle (car lst) (cdr lst))           ; сохраняем middle, заменяем num на первое число из списка, отбрасываем первый элемент из списка
          (if (= (abs (- num middle)) (abs (- (car lst) middle))) ; если модули разностей равны (числа одинаково удалены от середины) 
              (if (> num (car lst))                               ; если первый элемент меньше num
                  (find-nearest-num middle (car lst) (cdr lst))   ; да. сохраняем middle, заменяем num на первое число из списка, отбрасываем первый элемент из списка
                  (find-nearest-num middle num (cdr lst))         ; нет. отбрасываем первый элемент из списка, сохраняя всё остальное
               )
              (find-nearest-num middle num (cdr lst))             ; отбрасываем первый элемент из списка, сохраняя всё остальное
))))

(define (z2 lst)
  (if (empty? lst) ; если лахтин захочет впихнуть пустой список (o_o)
      #f           ; не-а
      (let ( (middle (find-middle lst 0 0)) (min-num (find-nearest-num (find-middle lst 0 0) (car lst) (cdr lst))))
                   ; middle - ср. арифм. списка
                   ; min-num - наименьшее ближайшее к среднему арифметическому (middle)
        (define (iter arr id)
          (if (empty? arr)                             ; база рекурсии
              null
              (if (= min-num (car arr))                ; если первый элемент из списка равен min-num
                  (cons id (iter (cdr arr) (+ id 1)))  ; да.  добавляем индекс вхождения к результату iter от оставшихся чисел и их индексов
                  (iter (cdr arr) (+ id 1))            ; нет. находим результат iter от оставшихся элементов и их индексов
         ))) 
        (cons min-num (iter lst 1)))))                 ; если индексация с нуля, то замени 1 на 0


;--------------------------------------------------------------------

(define (z3 lst)
  (define (iter arr num c res) ; arr - список, по которому пробегаемся, num - первое число, с и res - счетчики (в res сохраняется длина большей последовательности до предпоследней)
    (if (empty? arr)  ; база рекурсии
        (if (< c res) ; выводим длину большей последовательности
            res
            c
        )
        (if (not (xor (even? num) (even? (car arr)))) ; одинаковы ли чётности чисел
            (iter (cdr arr) (car arr) (+ c 1) res)    ; да. убираем первый элемент, заменяем num на первый элемент, увеличиваем c на 1, сохраняем res
            (if (> c res)                             ; нет
                (iter (cdr arr) (car arr) 1 c)        ; заменяем num на первый элемент, меняем c на res
                (iter (cdr arr) (car arr) 1 res)      ; заменяем num на первый элемент, сохраняем res
  ))))
  (cond
    [ (empty? lst) 0 ]                                ; если список пуст
    [ (empty? (cdr lst)) 1]                           ; если список одноэлементый
    [ else (iter (cdr lst) (car lst) 1 0) ]))

;---------------------------------------------------------------------

(define (z4 lst)
  (define (iter arr i) ; arr - список, по которому пробегаемся, i - индекс числа (со временем увеличивается)
    (if (empty? arr) ; база рекурсии
        '()
        (if (= i 0)                                       ; ZeroDivisionException
            (iter (cdr arr) (+ i 1))
            (if (= 0 (% (car arr) i))                     ; делит ли номер вхождения числа само число
                (cons (car arr) (iter (cdr arr) (+ i 1))) ; если да, добавляем число к списку, который вернет iter от последующих элементов
                (iter (cdr arr) (+ i 1))                  ; если нет, вызываем iter 
  ))))
  (iter lst 1))                                           ; если индексация начинается с нуля -> (iter lst 0)


;------------------------------------------------------------------

(define (reverseNum num res)                               ; разворачивает число: 123 -> 321; -123 -> -321
  (if (= num 0)                                            ; база рекурсии
      res
      (reverseNum (// num 10) (+ (* 10 res) (% num 10))))) ; добавляем первый разряд к res, умноженному на 10 


(define (z5 lst arr)                                            ; lst - изначальный список, arr - обратный ему с развернутымы элементами
  (define (iter foo res)                                        ; foo - список, по которому пробегаемся, res - развернутый список с "обратными" элементами
    (if (empty? foo)                                            ; база рекурсии
        res                                                     ; возращаем развёрнутый список с обратными элементами
        (iter (cdr foo) (cons (reverseNum (car foo) 0) res))))  ; отбрасываем первый элемент foo, добавляем развернутый элемент к res
  (or (equal? arr (iter lst '())) (equal? lst (iter arr '())))) ; если первый (развернутый) равен второму или наоборот
         
